// Copyright (c) 2012 SameGoal LLC.
// All Rights Reserved.
// Author: Andy Hochhaus <ahochhaus@samegoal.com>

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "net/http/server/event/event_server.h"

#include <gflags/gflags.h>
#include <glog/logging.h>
#include <event2/bufferevent.h>
#include <event2/bufferevent_ssl.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <re2/re2.h>

#include <map>
#include <string>
#include <vector>

#include "base/init.h"
#include "net/http/server/event/connection.h"
#include "net/http/server/event/handler.h"
#include "util/worker_pool/worker_pool.h"

// Until we have a compelling reason not to, this list should be kept in
// sync with the ciphers used by encrypted.google.com.
// https://www.ssllabs.com/ssltest/analyze.html?d=encrypted%2egoogle%2ecom
// http://www.imperialviolet.org/2011/11/22/forwardsecret.html
// http://www.imperialviolet.org/2012/03/02/ieecdhe.html
//
// Also note that some proxy software (eg: A86) can RST connections which use
// CBC 1/n-1 record splitting to mitigate the BEAST attack. For that reason
// among others prefer a RC4 based cipher.
//
// https://blog.torproject.org/blog/tor-and-beast-ssl-attack
// https://bugzilla.mozilla.org/show_bug.cgi?id=702111
// http://code.google.com/p/chromium/issues/detail?id=107728
const std::string kCipherSuites =
    TLS1_TXT_ECDHE_RSA_WITH_RC4_128_SHA ":"
    TLS1_TXT_ECDHE_RSA_WITH_AES_128_CBC_SHA ":"
    SSL3_TXT_RSA_RC4_128_SHA ":"
    SSL3_TXT_RSA_RC4_128_MD5 ":"
    TLS1_TXT_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ":"
    TLS1_TXT_ECDHE_RSA_WITH_AES_256_SHA384 ":"
    TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA ":"
    TLS1_TXT_RSA_WITH_AES_256_GCM_SHA384 ":"
    TLS1_TXT_RSA_WITH_AES_256_SHA256 ":"
    TLS1_TXT_RSA_WITH_AES_256_SHA ":"
    TLS1_TXT_ECDHE_RSA_WITH_DES_192_CBC3_SHA ":"
    SSL3_TXT_RSA_DES_192_CBC3_SHA ":"
    TLS1_TXT_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ":"
    TLS1_TXT_ECDHE_RSA_WITH_AES_128_SHA256 ":"
    TLS1_TXT_RSA_WITH_AES_128_GCM_SHA256 ":"
    TLS1_TXT_RSA_WITH_AES_128_SHA256 ":"
    TLS1_TXT_RSA_WITH_AES_128_SHA;

// Libevent Notes:
// * http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html
//   + After fork event_base must be event_reinit'ed.
//   + To avoid syscall overhead, do not call gettimeofday() directly but
//     instead call event_base_gettimeofday_cached() for the time libevent
//     began running the current round of callbacks.
// * http://www.wangafu.net/~nickm/libevent-book/Ref3_eventloop.html
//   + Use event_base_dump_events() for a list of current events.
// * http://www.wangafu.net/~nickm/libevent-book/Ref4_event.html
//   + To watch for POSIX style signals (eg: SIGTERM) use evsignal_new() and
//     associated evsignal_* macros.
//   + For common timeout values, use event_base_init_common_timeout().
// * http://www.wangafu.net/~nickm/libevent-book/Ref5_evutil.html
//   + Use evutil_socket_t instead of int for a socket.
//   + Use evutil_time* functions for platform independent timeval
//     manipulations.
//   + For accurate times use evutil_gettimeofday().
//   + Call evutil_make_socket_reusable() on listener sockets to ensure that
//     they are immediately reusable after the socket is closed.
//   + Use evutil_strtoll/evutil_snprintf/evutil_vsnprintf for portable
//     string manipulation functions.
//   + Use evutil_ascii_strcasecmp for ASCII based (non-locale dependent)
//     protocols.
//   + Use evutil_secure_rng_get_bytes() for cryptographically secure random
//     numbers (eg: cookies, session identifiers).

DEFINE_int32(limit_network_bytes_per_second, 0, "Limit network transfer rate. "
             "Use only for testing. 0 for no limit.");

namespace sg {
namespace net {
namespace http {
namespace server {
namespace event {

EventServer::EventServer(const std::string &ip,
                         const std::string &hostname,
                         const std::string &port,
                         const uint32 app_callback_pool_size)
    : app_callback_pool(app_callback_pool_size, false),
      host_(ip), hostname_(hostname), port_(port),
      listener_(NULL), close_cb_(nullptr), sctx_(NULL), read_timeout_(NULL),
      write_timeout_(NULL), rate_limit_tick_(NULL), rate_limit_cfg_(NULL),
      ssl_timeout_(0) {
  event_base_ = event_base_new();
  CHECK_NOTNULL(event_base_);
  if (FLAGS_limit_network_bytes_per_second > 0) {
    rate_limit_tick_ = new timeval_t;
    rate_limit_tick_->tv_sec = 0;
    // TODO(ahochhaus): Once openssl bufferevents have better support for
    // rate limiting, decrease the rate_limit_tick_ to 10ms to further smooth
    // traffic.
    // http://archives.seul.org/libevent/users/Dec-2011/msg00023.html
    rate_limit_tick_->tv_usec = 100000;  // 100 ms
    const uint64 bytes_per_ten_ms = FLAGS_limit_network_bytes_per_second / 10;
    LOG(INFO) << "bytes_per_ten_ms: " << bytes_per_ten_ms;
    rate_limit_cfg_ = ev_token_bucket_cfg_new(bytes_per_ten_ms,
                                              bytes_per_ten_ms + 1,
                                              bytes_per_ten_ms,
                                              bytes_per_ten_ms + 1,
                                              rate_limit_tick_);
    CHECK_NOTNULL(rate_limit_cfg_);
  }
}

EventServer::~EventServer() {
  delete read_timeout_;
  delete write_timeout_;
  if (rate_limit_cfg_) {
    ev_token_bucket_cfg_free(rate_limit_cfg_);
  }
  delete rate_limit_tick_;
  event_base_free(event_base_);
}

std::string EventServer::hostname() const {
  return hostname_;
}

std::string EventServer::port() const {
  return port_;
}

SSL_CTX *EventServer::sctx() {
  return sctx_;
}

void EventServer::Serve() {
  for (auto i = redirects_.begin(); i != redirects_.end(); ++i) {
    std::string host = i->host;
    std::string port = i->port;
    evutil_addrinfo_t *answer = DnsLookup(host, port);
    CHECK_NOTNULL(answer);
    i->listener = evconnlistener_new_bind(
        event_base_,
        &EventServer::Accept,
        this,
        LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_EXEC,
        -1,
        answer->ai_addr,
        answer->ai_addrlen);
    CHECK_NOTNULL(i->listener);

    evconnlistener_set_error_cb(i->listener, &EventServer::AcceptError);
  }

  evutil_addrinfo_t *answer = DnsLookup(host_, port_);
  CHECK_NOTNULL(answer);

  // TODO(ahochhaus): verify that LEV_OPT_THREADSAFE is not needed here.
  // http://www.wangafu.net/~nickm/libevent-book/Ref8_listener.html
  listener_ = evconnlistener_new_bind(
      event_base_,
      &EventServer::Accept,
      this,
      LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_EXEC,
      -1,
      answer->ai_addr,
      answer->ai_addrlen);
  CHECK_NOTNULL(listener_);

  evconnlistener_set_error_cb(listener_, &EventServer::AcceptError);
  // TODO(ahochhaus): Do we need to use evutil_make_socket_reusable()
  // on listener socket or use socket option?

  CHECK_GE(event_base_dispatch(event_base_), 0);
}

void EventServer::Terminate() {
  // See http://www.wangafu.net/~nickm/libevent-book/Ref3_eventloop.html
  // for comparison of event_base_loopbreak(event_base_) and
  // event_base_loopexit(event_base_, NULL)
  CHECK_EQ(event_base_loopbreak(event_base_), 0);

  app_callback_pool.WaitUntilNoWork();
}

event_base_t *EventServer::event_base() {
  return event_base_;
}

headers::Helper *EventServer::headers_helper() {
  return helper_;
}

void EventServer::set_read_timeout(const timeval_t &timeout) {
  delete read_timeout_;
  read_timeout_ = NULL;
  read_timeout_ = new timeval_t(timeout);
  CHECK_NOTNULL(read_timeout_);
}

void EventServer::set_write_timeout(const timeval_t &timeout) {
  delete write_timeout_;
  write_timeout_ = NULL;
  write_timeout_ = new timeval_t(timeout);
  CHECK_NOTNULL(write_timeout_);
}

void EventServer::set_ssl_timeout(int64 timeout) {
  ssl_timeout_ = timeout;
}

void EventServer::AddHandler(Handler *handler) {
  handlers_.push_back(handler);
}

void EventServer::AddRedirect(const std::string &host,
                              const std::string &port) {
  struct redirect redirect_info;
  redirect_info.host = host;
  redirect_info.port = port;
  redirect_info.listener = NULL;
  redirects_.push_back(redirect_info);
}

void EventServer::AddEncryptedRedirect(
    const std::string &/* host */,
    const std::string &/* port */,
    const std::string & /* crt */,
    const std::vector<std::string> &/* chain */,
    const std::string &/* key */) {
  // TODO(ahochhaus): remove this in favor of one EventServer per HTTPS port
}

void EventServer::SetHeadersHelper(headers::Helper *helper) {
  helper_ = helper;
}

void EventServer::EnableEncryption(const std::string &certificate,
                                   const std::vector<std::string> &chain_list,
                                   const std::string &private_key) {
  sctx_ = SSL_CTX_new(SSLv23_server_method());
  CHECK_NOTNULL(sctx_);
  if (SSL_CTX_use_certificate_ASN1(
          sctx_,
          certificate.length(),
          reinterpret_cast<const unsigned char*> (certificate.c_str())) != 1) {
    LOG(FATAL) << "SSL_CTX_use_certificate_ASN1: "
               << ERR_reason_error_string(ERR_get_error());
  }
  for (std::vector<std::string>::const_iterator i = chain_list.begin();
       i != chain_list.end(); ++i) {
    const unsigned char *crt = reinterpret_cast<const unsigned char*> (
        i->c_str());
    X509 *x509 = d2i_X509(NULL, &crt, i->length());
    CHECK_NOTNULL(x509);
    if (SSL_CTX_add_extra_chain_cert(sctx_, x509) != 1) {
      LOG(FATAL) << "SSL_CTX_add_extra_chain_cert: "
                 << ERR_reason_error_string(ERR_get_error());
    }
  }
  if (SSL_CTX_use_PrivateKey_ASN1(
          EVP_PKEY_RSA,
          sctx_,
          reinterpret_cast<const unsigned char*> (private_key.c_str()),
          private_key.length()) != 1) {
    LOG(FATAL) << "SSL_CTX_use_PrivateKey_ASN1: "
               << ERR_reason_error_string(ERR_get_error());
  }

  SSL_CTX_set_verify(sctx_, SSL_VERIFY_NONE, NULL);
  SSL_CTX_set_options(sctx_,
                      SSL_OP_ALL | SSL_MODE_RELEASE_BUFFERS |
                      SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2);
  if (ssl_timeout_) {
    SSL_CTX_set_timeout(sctx_, ssl_timeout_);
  }
  CHECK_EQ(1, SSL_CTX_set_cipher_list(sctx_, kCipherSuites.c_str()));
  // TODO(ahochhaus): enable SSL session caching. See evhtp for an example:
  // https://github.com/ellzey/libevhtp/blob/master/evhtp.c
  SSL_CTX_set_session_cache_mode(sctx_, SSL_SESS_CACHE_OFF);
  // SSL_CTX_sess_set_cache_size(sctx_, 4096);
  // SSL_CTX_sess_set_new_cb(sctx_, _evhtp_ssl_add_scache_ent);
  // SSL_CTX_sess_set_get_cb(sctx_, _evhtp_ssl_get_scache_ent);
  // SSL_CTX_sess_set_remove_cb(sctx_, _evhtp_ssl_delete_scache_ent);
}

void EventServer::SetConnectionCloseHandler(Callback callback) {
  close_cb_ = callback;
}

void EventServer::DispatchRequest(void *arg) {
  Request *request = static_cast<Request *> (arg);
  LOG(INFO) << "DispatchRequest uri: " << request->request_uri();
  bool request_handeled = false;
  for (auto handler = handlers_.begin();
       handler != handlers_.end(); ++handler) {
    if ((*handler)->Run(request)) {
      request_handeled = true;
      break;
    }
  }
  if (!request_handeled) {
    // TODO(ahochhaus): better not found page
    request->SendError(NOT_FOUND, "Document not found.");
  }
}

evutil_addrinfo_t *EventServer::DnsLookup(const std::string &host,
                                          const std::string &port) {
  evutil_addrinfo_t hints;
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_INET;  // ipv4 only
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP;
  hints.ai_flags = EVUTIL_AI_ADDRCONFIG;

  evutil_addrinfo_t *answer = NULL;
  if (evutil_getaddrinfo(host.c_str(), port.c_str(), &hints, &answer) != 0) {
    return NULL;
  }
  return answer;
}

void EventServer::Accept(evconnlistener_t *listener,
                         evutil_socket_t fd,
                         sockaddr_t *address,
                         int /* socklen */,
                         void *ctx) {
  EventServer *server = static_cast<EventServer *> (ctx);
  bufferevent_t *buff_ev;

  bool is_encrypted;
  if (listener == server->listener_ && server->sctx_) {
    is_encrypted = true;
    buff_ev = bufferevent_openssl_socket_new(
        server->event_base_,
        fd,
        SSL_new(server->sctx_),
        BUFFEREVENT_SSL_ACCEPTING,
        BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_THREADSAFE);
    // TODO(ahochhaus): allow dirty shutdown
    // bufferevent_openssl_set_allow_dirty_shutdown(buff_ev, 1);
  } else {
    is_encrypted = false;
    buff_ev = bufferevent_socket_new(
        server->event_base_,
        fd,
        BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_THREADSAFE);
  }
  CHECK_NOTNULL(buff_ev);

  if (server->rate_limit_cfg_) {
    CHECK_EQ(0, bufferevent_set_rate_limit(buff_ev, server->rate_limit_cfg_));
  }

  Connection *conn = new Connection(server, address, buff_ev, is_encrypted);
  if (server->read_timeout_) {
    conn->set_read_timeout(*server->read_timeout_);
  }
  if (server->write_timeout_) {
    conn->set_write_timeout(*server->write_timeout_);
  }
  if (server->close_cb_) {
    conn->SetConnectionCloseHandler(server->close_cb_);
  }
  conn->Init();
}

void EventServer::AcceptError(evconnlistener_t *listener, void *ctx) {
  LOG(ERROR) << "EventServer::AcceptError listener: " << listener
             << " ctx: " << ctx;
}

}  // namespace event
}  // namespace server
}  // namespace http
}  // namespace net
}  // namespace sg
